#pragma once

#include <CommonLib/Math/MathFunctions.h>
#include <CommonLib/Math/Vec2.h>

namespace fastbird
{
	class Mat33;
	class Vec3
	{
	public:
		Vec3() {}
		Vec3(float _x, float _y, float _z)
			: x(_x), y(_y), z(_z)
		{			
		}

		Vec3(Vec2 v2, float _z)
		{
			x = v2.x;
			y = v2.y;
			z = _z;
		}

		Vec3(const char* s);

		inline float Dot(const Vec3& vec) const
        {
            return x * vec.x + y * vec.y + z * vec.z;
        }

		inline float operator [] ( const size_t i ) const
        {
            assert( i < 3 );

            return *(&x+i);
        }

		inline float& operator [] ( const size_t i )
        {
            assert( i < 3 );

            return *(&x+i);
        }

		inline Vec3 operator - () const
        {
            return Vec3(-x, -y, -z);
        }

		inline Vec3 operator* (float scalar) const
		{
			return Vec3(x * scalar, y * scalar, z * scalar);
		}

		// v * m : means m is transposed.
		Vec3 operator* (const Mat33& r) const;

		inline Vec3 operator+ (const Vec3& r) const
		{
			return Vec3(x + r.x, y + r.y, z + r.z);
		}

		inline Vec3 operator- (const Vec3& r) const
		{
			return Vec3(x - r.x, y - r.y, z - r.z);
		}

		inline bool operator == ( const Vec3& r ) const
		{
			return ( x == r.x && y == r.y && z == r.z );
		}
		inline bool operator != (const Vec3& r ) const
		{
			return !operator==(r);
		}

		bool operator< (const Vec3& other) const;

		inline Vec3& operator+= (const Vec3& r)
		{
			x += r.x;
			y += r.y;
			z += r.z;
			return *this;
		}

		inline Vec3& operator-= (const Vec3& r)
		{
			x -= r.x;
			y -= r.y;
			z -= r.z;
			return *this;
		}

		inline Vec3& operator*= (const Vec3& r)
		{
			x *= r.x;
			y *= r.y;
			z *= r.z;
			return *this;
		}

		inline Vec3& operator*= (float f)
		{
			x *= f;
			y *= f;
			z *= f;
			return *this;
		}

		inline Vec3& operator/= (const Vec3& r)
		{
			x /= r.x;
			y /= r.y;
			z /= r.z;
			return *this;
		}

		inline Vec3& operator/= (float s)
		{
			x /= s;
			y /= s;
			z /= s;
			return *this;
		}

		inline Vec3 Cross( const Vec3& rVector ) const
        {
            return Vec3(
                y * rVector.z - z * rVector.y,
                z * rVector.x - x * rVector.z,
                x * rVector.y - y * rVector.x);
        }

		inline float Normalize()
		{
			float length = sqrt(x*x + y*y + z*z);
			if (length > 0.0f)
			{
				float invLength = 1.f / length;
				x*= invLength;
				y*= invLength;
				z*= invLength;
			}

			return length;
		}

		inline Vec3 NormalizeCopy()
		{
			Vec3 result = *this;
			result.Normalize();
			return result;
		}

		inline float Length() const
		{
			return sqrt(x*x + y*y + z*z);
		}

		inline float LengthSQ() const
		{
			return x*x + y*y + z*z;
		}

		inline float AngleBetween(const Vec3& v) const
		{
			float lenProduct = Length() * v.Length();

			// Prevent dividing zero
			if(lenProduct < 1e-6f)
				lenProduct = 1e-6f;

			float f = Dot(v) / lenProduct;
			
			Clamp(f, -1.0f, 1.0f);
			return ACos(f);

		}

	public:
		float x, y, z;

		static const Vec3 UNIT_X;
		static const Vec3 UNIT_Y;
		static const Vec3 UNIT_Z;
		static const Vec3 ZERO;
	};

	inline Vec3 operator* (float l, const Vec3& r)
	{
		return Vec3(r.x*l, r.y*l, r.z*l);
	}
}